﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace NetAssist.Math
{
  /// <summary>
  /// Provides methods that analyze a set of numbers and yields a formula that represents the sequence.
  /// </summary>
  public class Analyzer
  {
    private Func<double, double> formula;
    private List<Term> terms;

    /// <summary>
    /// Initializes a new analyzer with the set of values to analyze.
    /// </summary>
    /// <param name="inputs">The set of numbers to analyze.</param>
    public Analyzer(IEnumerable<double> inputs)
    {
      try
      {
        this.terms = GetTerms(inputs.Select(I => (BigReal)I));
        this.formula = GetFormula(terms); ;
      }
      catch (Exception ex)
      {
        throw new SequenceTooComplexException("The provided sequence is too complex, Optimization and detection failed.", ex);
      }
    }

    /// <summary>
    /// The formula that represents the set of numbers provided to the analyzer.
    /// </summary>
    public Func<double, double> Formula { get { return formula; } }

    /// <summary>
    /// The the string representation of the formula generated by the analyzer.
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
      StringBuilder res = new StringBuilder();
      bool start = true;
      foreach (Term term in terms)
      {
        string coefficient = term.Coefficient.ToString();

        if (term.Coefficient == 0)
          continue;
        else if (coefficient == "1" && term.Power != 0)
          coefficient = "";
        else if (coefficient == "-1" && term.Power != 0)
          coefficient = "-";

        if (start)
          start = false;
        else if (term.Coefficient < 0)
          coefficient = " - " + coefficient.Substring(1);
        else
          res.Append(" + ");

        switch (term.Power)
        {
          case 0:
            res.Append(coefficient);
            break;
          case 1:
            res.AppendFormat("{0}X", coefficient);
            break;
          default:
            res.AppendFormat("{0}X^{1}", coefficient, term.Power);
            break;
        }
      }
      return res.ToString();
    }

    private static List<Term> GetTerms(IEnumerable<BigReal> inputs)
    {
      List<Term> terms = new List<Term>();
      do
      {
        Term temp = GetTerm(0, inputs);
        terms.Add(temp);
        if (temp.Power == 0)
          break;
        inputs = RefactorInputs(temp, inputs).ToList();
      } while (true);

      return terms;
    }

    private static Func<double, double> GetFormula(IEnumerable<Term> terms)
    {
      ParameterExpression parameter = Expression.Parameter(typeof(double), "x");
      Expression exp = GetFormulaExpression(parameter, terms);
      return Expression.Lambda<Func<double, double>>(exp, parameter).Compile();
    }

    private static Expression GetFormulaExpression(ParameterExpression parameter, IEnumerable<Term> terms)
    {
      Expression baseExp = GetSingleExp(parameter, terms.First());
      foreach (Term term in terms.Skip(1))
      {
        Expression exp = GetSingleExp(parameter, term);
        baseExp = Expression.Add(baseExp, exp);
      }

      return baseExp;
    }

    private static Expression GetSingleExp(ParameterExpression parameter, Term term)
    {
      ConstantExpression factor = Expression.Constant((double)term.Coefficient, typeof(double));
      ConstantExpression power = Expression.Constant((double)term.Power, typeof(double));

      Expression pow = Expression.Power(parameter, power);
      return Expression.Multiply(factor, pow);
    }

    private static IEnumerable<BigReal> RefactorInputs(Term term, IEnumerable<BigReal> inputs)
    {
      int idx = 0;
      using (IEnumerator<BigReal> rator = inputs.GetEnumerator())
        while (rator.MoveNext())
          yield return rator.Current - (term.Coefficient * Power(idx++ + 1, term.Power));
    }

    private static IEnumerable<BigReal> Derive(IEnumerable<BigReal> inputs)
    {
      using (IEnumerator<BigReal> rator1 = inputs.GetEnumerator())
      using (IEnumerator<BigReal> rator2 = inputs.GetEnumerator())
      {
        if (rator2.MoveNext())
          while (rator2.MoveNext())
          {
            rator1.MoveNext();
            yield return rator2.Current - rator1.Current;
          }
      }
    }

    private static Term GetTerm(int level, IEnumerable<BigReal> inputs)
    {
      BigReal temp = inputs.FirstOrDefault();
      if (inputs.Skip(1).All(X => X == temp))
        return new Term() { Coefficient = new BigReal(inputs.First(), (int)Factorial(level)), Power = level, };

      return GetTerm(++level, Derive(inputs));
    }

    private static long Factorial(int level)
    {
      long f = 1;

      for (int i = 2; i <= level; i++)
        f = f * i;

      return f;
    }
        
    private static long Power(int x, int y)
    {
      long res = x;
      for (int i = 1; i < y; i++)
        res = res * x;

      return res;
    }

    private struct Term
    {
      public BigReal Coefficient { get; set; }
      public int Power { get; set; }
    }
  }
}



